<template>
	<view class="uni-file-picker">
		<view v-if="title" class="uni-file-picker__header">
			<text class="file-title">{{ title }}</text>
			<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
		</view>
		<upload-image
			v-if="fileMediatype === 'image' && showType === 'grid'"
			:readonly="readonly"
			:image-styles="imageStyles"
			:files-list="filesList"
			:limit="limitLength"
			:disablePreview="disablePreview"
			:delIcon="delIcon"
			@uploadFiles="uploadFiles"
			@choose="choose"
			@delFile="delFile"
		>
			<slot>
				<view class="is-add">
					<view class="icon-add"></view>
					<view class="icon-add rotate"></view>
				</view>
			</slot>
		</upload-image>
		<upload-file
			v-if="fileMediatype !== 'image' || showType !== 'grid'"
			:readonly="readonly"
			:list-styles="listStyles"
			:files-list="filesList"
			:showType="showType"
			:delIcon="delIcon"
			@uploadFiles="uploadFiles"
			@choose="choose"
			@delFile="delFile"
		>
			<slot><button type="primary" size="mini">选择文件</button></slot>
		</upload-file>
	</view>
</template>

<script>
	import { chooseAndUploadFile } from './choose-and-upload-file.js'
	import uploadImage from './upload-image.vue'
	import uploadFile from './upload-file.vue'
	let fileInput = null
/**
 * FilePicker 文件选择上传
 * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
 * @property {Object|Array}	value	组件数据，通常用来回显 ,类型由return-type属性决定
 * @property {Boolean}	disabled=[true|false]	组件禁用
 * 	@value true 	禁用
 * 	@value false 	取消禁用
 * @property {Boolean}	readonly=[true|false]	组件只读，不可选择，不显示进度，不显示删除按钮
 * 	@value true 	只读
 * 	@value false 	取消只读
 * @property {String}	return-type=[array|object]	限制 value 格式，当为 object 时 ，组件只能单选，且会覆盖
 * 	@value array	规定 value 属性的类型为数组
 * 	@value object	规定 value 属性的类型为对象
 * @property {Boolean}	disable-preview=[true|false]	禁用图片预览，仅 mode:grid 时生效
 * 	@value true 	禁用图片预览
 * 	@value false 	取消禁用图片预览
 * @property {Boolean}	del-icon=[true|false]	是否显示删除按钮
 * 	@value true 	显示删除按钮
 * 	@value false 	不显示删除按钮
 * @property {Boolean}	auto-upload=[true|false]	是否自动上传，值为true则只触发@select,可自行上传
 * 	@value true 	自动上传
 * 	@value false 	取消自动上传
 * @property {Number|String}	limit	最大选择个数 ，h5 会自动忽略多选的部分
 * @property {String}	title	组件标题，右侧显示上传计数
 * @property {String}	mode=[list|grid]	选择文件后的文件列表样式
 * 	@value list 	列表显示
 * 	@value grid 	宫格显示
 * @property {String}	file-mediatype=[image|video|all]	选择文件类型
 * 	@value image	只选择图片
 * 	@value video	只选择视频
 * 	@value all		选择所有文件
 * @property {Array}	file-extname	选择文件后缀，根据 file-mediatype 属性而不同
 * @property {Object}	list-style	mode:list 时的样式
 * @property {Object}	image-styles	选择文件后缀，根据 file-mediatype 属性而不同
 * @event {Function} select 	选择文件后触发
 * @event {Function} progress 文件上传时触发
 * @event {Function} success 	上传成功触发
 * @event {Function} fail 		上传失败触发
 * @event {Function} delete 	文件从列表移除时触发
 */
export default {
	name: 'uniFilePicker',
	components: {
		uploadImage,
		uploadFile
	},
	props: {
		value: {
			type: [Array, Object],
			default() {
				return []
			}
		},
		disabled: {
			type: Boolean,
			default: false
		},
		disablePreview: {
			type: Boolean,
			default: false
		},
		delIcon: {
			type: Boolean,
			default: true
		},
		// 自动上传
		autoUpload: {
			type: Boolean,
			default: true
		},
		// 最大选择个数 ，h5只能限制单选或是多选
		limit: {
			type: [Number, String],
			default: 9
		},
		// 列表样式 grid | list | list-card
		mode: {
			type: String,
			default: 'grid'
		},
		// inputUrl: {
		// 	type: Boolean,
		// 	default: false
		// },
		// 选择文件类型  image/video/all
		fileMediatype: {
			type: String,
			default: 'image'
		},
		// 文件类型筛选
		fileExtname: {
			type: [Array, String],
			default() {
				return []
			}
		},
		title: {
			type: String,
			default: ''
		},
		listStyles: {
			type: Object,
			default() {
				return {
					// 是否显示边框
					border: true,
					// 是否显示分隔线
					dividline: true,
					// 线条样式
					borderStyle: {}
				}
			}
		},
		imageStyles: {
			type: Object,
			default() {
				return {
					width: 'auto',
					height: 'auto'
				}
			}
		},
		readonly: {
			type: Boolean,
			default: false
		},
		returnType: {
			type: String,
			default: 'array'
		}
	},
	watch: {
		value: {
			handler(newVal) {
				let newFils = []
				let newData = [].concat(newVal || [])
				newData.forEach(v => {
					const files = this.files.find(i => i.url === v.url)
					const reg = /cloud:\/\/([\w.]+\/?)\S*/
					if (!v.path) {
						v.path = v.url
					}
					if (reg.test(v.url)) {
						this.getTempFileURL(v, v.url)
					}
					newFils.push(files ? files : v)
				})
				this.files = newFils
			},
			immediate: true
		}
	},
	data() {
		return {
			files: [],
		}
	},
	computed: {
		filesList() {
			let files = []
			this.files.forEach(v => {
				files.push(v)
			})
			return files
		},
		showType() {
			if (this.fileMediatype === 'image') {
				return this.mode
			}
			return 'list'
		},
		limitLength() {
			if (this.returnType === 'object') {
				return 1
			}
			if (!this.limit) {
				return 1
			}
			if (this.limit >= 9) {
				return 9
			}
			return this.limit
		},
		extname(){
			if (!Array.isArray(this.fileExtname)) {
				let extname = this.fileExtname.replace(/(\[|\])/g,'')
				return extname.split(',')
			} else {
				return this.fileExtname
			}
			return []
		}
	},
	created() {
		// this.files = Object.assign([], this.value)
		this.tempData = {}

	},
	methods: {
		/**
		 * 继续上传
		 */
		upload() {
			// TODO 先放弃这个实现 ，不能全部上传
			// if (this.$uploadFiles) {
			// 	this.$uploadFiles()
			// } else {
			// 	uni.showToast({
			// 		title: '请先选择文件',
			// 		icon: 'none'
			// 	})
			// }
			let files = []
			this.files.forEach((v, index) => {
				if (v.status === 'ready' || v.status === 'error') {
					files.push(Object.assign({}, v))
				}
			})

			this.uploadFiles(files)
		},
		/**
		 * 选择文件
		 */
		choose() {
			if (this.disabled) return
			if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType === 'array') {
				uni.showToast({
					title: `您最多选择 ${this.limitLength} 个文件`,
					icon: 'none'
				})
				return
			}
			// uni.showActionSheet({
			// 	itemList: ['填写 url 地址', '选择文件'],
			// 	success: (res) => {
			// 		if (res.tapIndex === 1) {
			// 			this.chooseFiles()
			// 		}
			// 	},
			// 	fail: function(res) {}
			// });
			this.chooseFiles()
		},

		/**
		 * 选择文件并上传
		 */
		chooseFiles() {
			// API 正式发布前，使用本地API上传函数
			if (!uniCloud.chooseAndUploadFile) {
				uniCloud.chooseAndUploadFile = chooseAndUploadFile
			}

			uniCloud
				.chooseAndUploadFile({
					type: this.fileMediatype,
					compressed: false,
					// TODO 如果为空，video 有问题
					extension: this.extname.length > 0 ? this.extname : undefined,
					count: this.limitLength - this.files.length, //默认9
					onChooseFile: async res => {
						if ((Number(this.limitLength) === 1 && this.disablePreview && !this.disabled) || this.returnType === 'object') {
							this.files = []
						}
						let filePaths = []
						let files = []
						if (this.extname && this.extname.length > 0) {
							res.tempFiles.forEach(v => {
								let fileFullName = this.getFileExt(v.name)
								const extname = fileFullName.ext.toLowerCase()
								if (this.extname.indexOf(extname) !== -1) {
									files.push(v)
									filePaths.push(v.path)
								}
							})
							if (files.length !== res.tempFiles.length) {
								uni.showToast({
									title: `当前选择了${res.tempFiles.length}个文件 ，${res.tempFiles.length - files.length} 个文件格式不正确`,
									icon: 'none',
									duration: 5000
								})
							}
						} else {
							filePaths = res.tempFilePaths
							files = res.tempFiles
						}

						let currentData = []
						for (let i = 0; i < files.length; i++) {
							if (this.limitLength - this.files.length <= 0) break
							files[i].uuid = Date.now()
							let filedata = await this.getFileData(files[i], this.fileMediatype)
							filedata.progress = 0
							filedata.status = 'ready'
							this.files.push(filedata)
							currentData.push(filedata)
						}
						this.$emit('select', {
							tempFiles: currentData,
							tempFilePaths: filePaths
						})
						res.tempFiles = files
						// 停止自动上传
						if (!this.autoUpload) {
							res.tempFiles = []
							// TODO 先放弃这个实现 ，不能全部上传
							// return new Promise((resolve) => {
							// 	this.$uploadFiles = () => {
							// 		// this._is_uploading = true
							// 		resolve(res)
							// 	}
							// })
						}
					},
					onUploadProgress: progressEvent => {
						this.setProgress(progressEvent, progressEvent.index)
					}
				})
				.then(result => {
					this.setSuccessAndError(result.tempFiles)
				})
				.catch(err => {
					console.log('选择失败', err)
				})
		},

		/**
		 * 批传
		 * @param {Object} e
		 */
		uploadFiles(files) {
			files = [].concat(files)
			this.uploadCloudFiles(files, 5, res => {
				this.setProgress(res, res.index, true)
			})
				.then(result => {
					this.setSuccessAndError(result)
				})
				.catch(err => {
					console.log('err', err)
				})
		},

		/**
		 * 成功或失败
		 */
		async setSuccessAndError(res, fn) {
			let successData = []
			let errorData = []
			let tempFilePath = []
			let errorTempFilePath = []
			for (let i = 0; i < res.length; i++) {
				// const index  = item.index
				const item = res[i]
				const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
				if (index === -1 || !this.files) break
				if (item.errMsg === 'request:fail') {
					this.files[index].url = item.path
					this.files[index].status = 'error'
					this.files[index].errMsg = item.errMsg
					this.files[index].progress = -1
					errorData.push(this.files[index])
					errorTempFilePath.push(this.files[index].url)
				} else {
					this.files[index].errMsg = ''
					this.files[index].url = item.url
					this.files[index].status = 'success'
					successData.push(this.files[index])
					tempFilePath.push(this.files[index].url)
				}
			}

			if (successData.length > 0) {
				this.setEmit()
				// 状态改变返回
				this.$emit('success', {
					tempFiles: this.backObject(successData),
					tempFilePaths: tempFilePath
				})
			}

			if (errorData.length > 0) {
				this.$emit('fail', {
					tempFiles: this.backObject(errorData),
					tempFilePaths: errorTempFilePath
				})
			}
		},

		/**
		 * 获取进度
		 * @param {Object} progressEvent
		 * @param {Object} index
		 * @param {Object} type
		 */
		setProgress(progressEvent, index, type) {
			const fileLenth = this.files.length
			const percentNum = (index / fileLenth) * 100
			const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
			let idx = index
			if (!type) {
				idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
			}
			if (idx === -1 || !this.files[idx]) return
			this.files[idx].progress = percentCompleted
			// 上传中
			this.$emit('progress', {
				index: idx,
				progress: parseInt(percentCompleted),
				tempFile: this.files[idx]
			})
		},

		/**
		 * 删除
		 * @param {Object} index
		 */
		delFile(index) {
			this.$emit('delete', {
				tempFile: this.files[index],
				tempFilePath: this.files[index].url
			})
			this.files.splice(index, 1)
		},

		/**
		 * 获取文件名和后缀
		 * @param {Object} name
		 */
		getFileExt(name) {
			const last_len = name.lastIndexOf('.')
			const len = name.length
			return {
				name: name.substring(0, last_len),
				ext: name.substring(last_len + 1, len)
			}
		},

		/**
		 * 获取图片信息
		 * @param {Object} filepath
		 */
		getFileInfo(filepath) {
			return new Promise((resolve, reject) => {
				uni.getImageInfo({
					src: filepath,
					success(res) {
						resolve(res)
					},
					fail(err) {
						reject(err)
					}
				})
			})
		},

		/**
		 * 获取封装数据
		 */
		async getFileData(files, type = 'image') {
			// 最终需要上传数据库的数据
			let fileFullName = this.getFileExt(files.name)
			const extname = fileFullName.ext.toLowerCase()
			let filedata = {
				name: files.name,
				uuid: files.uuid,
				extname: extname || '',
				cloudPath: files.cloudPath,
				fileType: files.fileType,
				url: files.path || files.path,
				size: files.size, //单位是字节
				image: {},
				path: files.path,
				video: {}
			}
			if (type === 'image') {
				const imageinfo = await this.getFileInfo(files.path)
				filedata.image.width = imageinfo.width
				filedata.image.height = imageinfo.height
				filedata.image.location = imageinfo.path
			}
			return filedata
		},

		/**
		 * 批量上传
		 */
		uploadCloudFiles(files, max = 5, onUploadProgress) {
			files = JSON.parse(JSON.stringify(files))
			const len = files.length
			let count = 0
			let self = this
			return new Promise(resolve => {
				while (count < max) {
					next()
				}

				function next() {
					let cur = count++
					if (cur >= len) {
						!files.find(item => !item.url && !item.errMsg) && resolve(files)
						return
					}
					const fileItem = files[cur]
					const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
					fileItem.url = ''
					delete fileItem.errMsg

					uniCloud
						.uploadFile({
							filePath: fileItem.path,
							cloudPath: fileItem.cloudPath,
							fileType: fileItem.fileType,
							onUploadProgress: res => {
								res.index = index
								onUploadProgress && onUploadProgress(res)
							}
						})
						.then(res => {
							fileItem.url = res.fileID
							fileItem.index = index
							if (cur < len) {
								next()
							}
						})
						.catch(res => {
							fileItem.errMsg = res.errMsg || res.message
							fileItem.index = index
							if (cur < len) {
								next()
							}
						})
				}
			})
		},
		setEmit() {
			let data = []
			if (this.returnType === 'object') {
				data = this.backObject(this.files)[0]
			} else {
				data = this.backObject(this.files)
			}
			this.$emit('input', data)
		},
		backObject(files) {
			let newFilesData = JSON.parse(JSON.stringify(files))
			newFilesData.map(v => {
				delete v.path
				delete v.uuid
				delete v.video
				delete v.progress
				delete v.errMsg
				delete v.status
				delete v.cloudPath
				return v
			})
			return newFilesData
		},
		async getTempFileURL(file, fileList) {
			fileList = {
				fileList: [].concat(fileList)
			}
			const urls = await uniCloud.getTempFileURL(fileList)
			file.path = urls.fileList[0].tempFileURL || ''
			const index = this.files.findIndex(v => v.path === file.path)
			if (index !== -1) {
				this.$set(this.files, index, file)
			}
		}
	}
}
</script>

<style>
.uni-file-picker {
	/* #ifndef APP-NVUE */
	box-sizing: border-box;
	overflow: hidden;
	/* #endif */
}

.uni-file-picker__header {
	padding-top: 5px;
	padding-bottom: 10px;
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	justify-content: space-between;
}

.file-title {
	font-size: 14px;
	color: #333;
}

.file-count {
	font-size: 14px;
	color: #999;
}

.is-add {
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	align-items: center;
	justify-content: center;
}

.icon-add {
	width: 50px;
	height: 5px;
	background-color: #f1f1f1;
	border-radius: 2px;
}

.rotate {
	position: absolute;
	transform: rotate(90deg);
}
</style>
